package com.wondersgroup.restbase.dto;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.Entity;

import org.apache.log4j.Logger;

import com.wondersgroup.restbase.entity.CreateDateTime;
import com.wondersgroup.restbase.entity.TimeTrackingData;
import com.wondersgroup.restbase.util.AccessorUtils;

/**
 * dto和业务实体转换的工具类
 * @author wondersgroup
 *
 */
@SuppressWarnings({"unchecked","rawtypes"})
public abstract class DtoUtils {
	static Logger logger = Logger.getLogger(DtoUtils.class);

	/**
	 * 将业务实体集合转换成dto集合的方法
	 * @param models 业务实体集合
	 * @param dtoClass dto的类名
	 * @return dto的集合
	 * @throws Exception 转换错误时抛出异常
	 */
	public static <DTO, MODEL> List<DTO> toDto(List<MODEL> models, Class<DTO> dtoClass) throws Exception {
		if (models == null || models.size() == 0)
			return null;
		List<DTO> list = new ArrayList<DTO>();
		for (MODEL model : models) {
			DTO dto = toDto(model, dtoClass);
			list.add(dto);
		}
		return list;
	}

	/**
	 * 将业务实体转换成dto的方法
	 * @param model 业务实体集合
	 * @param dtoClass dto的类名
	 * @return dto对象
	 * @throws Exception 转换错误时抛出异常
	 */
	public static <DTO, MODEL> DTO toDto(MODEL model, Class<DTO> dtoClass) throws Exception {
		List<DtoFieldMapInfo> mapList = DtoFieldMapInfo.build(dtoClass);
		DTO dto = dtoClass.newInstance();
		for (DtoFieldMapInfo mapInfo : mapList) {
			if (!mapInfo.isOutput())
				continue;
			if (mapInfo.getModelField().indexOf(".") > 0) {
				String[] fields = mapInfo.getModelField().split("\\.");
				Object obj = model;
				for (String field : fields) {
					if (obj == null)
						break;
					obj = AccessorUtils.findGetter(obj, field).invoke(obj);
				}
				Method setter = AccessorUtils.findSetter(dto, mapInfo.getThisField());
				setter.invoke(dto, obj);
			} else {
				Method getter = AccessorUtils.findGetter(model, mapInfo.getModelField());
				Method setter = AccessorUtils.findSetter(dto, mapInfo.getThisField());
				// modify by wym 增加对日期类型的处理
				Class returnType = getter.getReturnType();
				String classPath = returnType.getName();
				if (returnType == java.util.Date.class) {
					// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd
					// HH:mm:ss");
					SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
					Object o = getter.invoke(model);
					String dateStr = "";
					if (o != null) {
						dateStr = sdf.format(o).toString();
					}
					setter.invoke(dto, dateStr);
				} else if (returnType == java.lang.Integer.class) {
					Object valueInt = getter.invoke(model);
					setter.invoke(dto, valueInt == null ? "" : valueInt.toString());
				} else if (returnType.getAnnotation(Entity.class) != null) {
					Object modelTmp = getter.invoke(model);
					if (modelTmp != null) {
						DtoMapping dtoMapping = modelTmp.getClass().getAnnotation(DtoMapping.class);
						String dtoPath = dtoMapping.className();
						Class clazz = Class.forName(dtoPath);
						Object dtoTmp = toDto(modelTmp, clazz);
						setter.invoke(dto, dtoTmp);
					}
				} else if (classPath.startsWith("java.util.List")) {
					List<Object> modelTmp = (List<Object>) getter.invoke(model);
					List<Object> dtos = new ArrayList<Object>();
					if (modelTmp != null) {
						for (int i = 0; i < modelTmp.size(); i++) {
							DtoMapping dtoMapping = modelTmp.getClass().getAnnotation(DtoMapping.class);
							String dtoPath = dtoMapping.className();
							Class clazz = Class.forName(dtoPath);
							Object dtoTmp = toDto(modelTmp, clazz);
							dtos.add(dtoTmp);
						}
						setter.invoke(dto, dtos);
					}
				} else {
					setter.invoke(dto, getter.invoke(model));
				}

			}
		}

		return dto;
	}

	/**
	 * 将List<Dto>转换为List<map> 使用NativeQueryMapInfo
	 * @param dtoList
	 * @param dtoClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO, MODEL> List<Map<String, String>> NativeListDtoToListMap(List<DTO> dtoList, Class<DTO> dtoClass)
			throws Exception {
		List<Map<String, String>> listMap = new ArrayList<Map<String, String>>();
		for (DTO dto : dtoList) {
			Map<String, String> map = NativeDtoToMap(dto, dtoClass);
			listMap.add(map);
		}
		return listMap;
	}

	/**
	 * Dto转换为map使用NativeQueryMapInfo
	 * @param dto
	 * @param dtoClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO, MODEL> Map<String, String> NativeDtoToMap(DTO dto, Class<DTO> dtoClass) throws Exception {
		List<NativeQueryMapInfo> mapList = NativeQueryMapInfo.build(dtoClass);
		Map<String, String> map = new HashMap<String, String>();
		for (NativeQueryMapInfo mapInfo : mapList) {
			String field = mapInfo.getDtoField();
			Method getter = AccessorUtils.findGetter(dto, field);
			Object o = getter.invoke(dto);
			String value = String.valueOf(o).trim();
			if (!value.equals("")) {
				map.put(field, String.valueOf(o));
			}
		}

		return map;
	}

	/**
	 * 通过dto创建业务实体的方法
	 * @param dto 数据传输对象
	 * @param modelClass 业务实体类对象
	 * @return 业务实体对象
	 * @throws Exception 转换错误时抛出异常
	 */
	public static <DTO, MODEL> MODEL newModel(DTO dto, Class<MODEL> modelClass) throws Exception {
		MODEL model = modelClass.newInstance();
		List<DtoFieldMapInfo> mapList = DtoFieldMapInfo.build(dto.getClass());

		for (DtoFieldMapInfo mapInfo : mapList) {
			mapInfo.setModelField(dto, model);
		}

		return model;
	}

	/**
	 * 
	 * @param dto
	 * @param modelClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO, MODEL> MODEL nativeDtoToModel(DTO dto, Class<MODEL> modelClass) throws Exception {
		MODEL model = modelClass.newInstance();
		List<NativeQueryMapInfo> mapList = NativeQueryMapInfo.build(dto.getClass());

		for (NativeQueryMapInfo mapInfo : mapList) {
			mapInfo.setModelField(dto, model);
		}

		return model;
	}

	/**
	 * 
	 * @param dto
	 * @param model
	 * @return
	 * @throws Exception
	 */
	public static <DTO, MODEL> MODEL update(DTO dto, MODEL model) throws Exception {
		List<DtoFieldMapInfo> mapList = DtoFieldMapInfo.build(dto.getClass());

		for (DtoFieldMapInfo mapInfo : mapList) {
			mapInfo.setModelField(dto, model);
		}

		return model;
	}
	
	/**
	 * 
	 * @param map
	 * @param dtoClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO> DTO NativeQueryMapToOutput(Map map, Class<DTO> dtoClass) throws Exception {
		if (map == null)
			return null;
		List<NativeQueryMapInfo> mapList = NativeQueryMapInfo.build(dtoClass);
		DTO dto = dtoClass.newInstance();
		for (NativeQueryMapInfo mapInfo : mapList) {
			if (mapInfo.isOutput()) {
				Object queryValue = map.get(mapInfo.getNativeQueryField().toUpperCase());
				Method setter = AccessorUtils.findSetter(dto, mapInfo.getDtoField());
				if (queryValue == null || "".equals(queryValue)) {
					setter.invoke(dto, "");
					continue;
				}

				if (queryValue instanceof Integer || queryValue instanceof BigInteger) {
					queryValue = String.valueOf(queryValue);
				} else if (queryValue instanceof Timestamp || queryValue instanceof Date) {
					SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
					// SimpleDateFormat sdf = new
					// SimpleDateFormat("yyyy-MM-dd");
					queryValue = sdf.format(queryValue).toString();
				} else if (queryValue instanceof BigDecimal) {
					String tempStr = String.valueOf(queryValue);
					if (tempStr.indexOf(".") > 0) {
						DecimalFormat myformat = new DecimalFormat("#.##");
						queryValue = myformat.format(queryValue);
					} else {
						queryValue = String.valueOf(queryValue);
					}

				}
				setter.invoke(dto, queryValue);
			}
		}
		return dto;
	}

	/**
	 * 
	 * @param maps
	 * @param dtoClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO> List<DTO> NativeQueryMapToOutput(List<Map> maps, Class<DTO> dtoClass) throws Exception {
		if (maps == null || maps.size() == 0)
			return null;
		List<DTO> list = new ArrayList<DTO>();
		for (Map map : maps) {
			DTO dto = NativeQueryMapToOutput(map, dtoClass);
			list.add(dto);
		}
		return list;
	}

	/**
	 * 
	 * @param input
	 * @param dtoClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO> Map NativeQueryInputToMap(Map input, Class<DTO> dtoClass) throws Exception {
		if (input == null)
			return null;
		List<NativeQueryMapInfo> mapList = NativeQueryMapInfo.build(dtoClass);
		Map map = new HashMap();
		for (NativeQueryMapInfo mapInfo : mapList) {
			if (mapInfo.isInput()) {
				String nativeKey = mapInfo.getNativeQueryField();
				Object dtoKey = mapInfo.getDtoField();
				map.put(nativeKey, input.get(dtoKey));
			}
		}
		return map;
	}

	/**
	 * 
	 * @param map
	 * @param dtoClass
	 * @return
	 * @throws Exception
	 */
	public static <DTO> DTO mapToDtoService(Map<String, String> map, Class<DTO> dtoClass) throws Exception {
		DTO dto = dtoClass.newInstance();
		Iterator<String> it = map.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			String val = map.get(key);
			setValue(dto, key, val);
		}
		return dto;
	}

	/**
	 * 
	 * @param dto
	 * @param prop
	 * @param value
	 * @throws Exception
	 */
	private static <DTO> void setValue(DTO dto, String prop, String value) throws Exception {
		Method setter = AccessorUtils.findSetter(dto, prop);
		logger.debug("setValue: prop= " + prop + ", val=" + value);
		if (setter == null) {
			logger.error("prop=" + prop + "not found in dto!");
		} else {
			setter.invoke(dto, cast(prop, value, setter.getParameterTypes()[0]));

		}
	}

	/**
	 * 
	 * @param prop
	 * @param value
	 * @param clazz
	 * @return
	 */
	private static Object cast(String prop, String value, Class clazz) {
		if (clazz.equals(Integer.class)) {
			return Integer.valueOf(value);
		} else if (clazz.equals(Timestamp.class) || clazz.equals(Date.class)) {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			return sdf.format(value).toString();
		}
		return value;
	}

	/**
	 * 
	 * @param dto
	 * @param modelClass
	 * @return
	 */
	public static <MODEL, DTO> MODEL newModelService(DTO dto, Class<MODEL> modelClass) {
		if (dto == null)
			return null;
		try {
			MODEL model = DtoUtils.newModel(dto, modelClass);
			newTrack(model, modelClass);
			return model;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 
	 * @param model
	 * @param modelClass
	 * @throws IllegalAccessException
	 */
	private static <MODEL> void newTrack(MODEL model, Class<MODEL> modelClass) throws IllegalAccessException {
		Field trackField = getTrackField(model, modelClass);
		if (trackField != null) {
			if (trackField.getType().equals(CreateDateTime.class)) {
				trackField.setAccessible(true);
				trackField.set(model, new CreateDateTime(new Date()));
			} else {
				trackField.setAccessible(true);
				trackField.set(model, new TimeTrackingData(new Date(), new Date()));
			}
		}
	}

	/**
	 * 
	 * @param model
	 * @param modelClass
	 * @return
	 */
	private static <MODEL> Field getTrackField(MODEL model, Class<MODEL> modelClass) {
		Field trackField = null;
		for (Field field : modelClass.getDeclaredFields()) {
			// System.out.println("field: " + field.getName() + " type:" +
			// field.getType());
			if (field.getType().equals(TimeTrackingData.class) || field.getType().equals(CreateDateTime.class)) {
				trackField = field;
				break;
			}
		}
		return trackField;
	}

	/**
	 * 
	 * @param map
	 * @param dto
	 * @return
	 * @throws Exception
	 */
	public static <DTO> DTO mapToDtoValues(Map<String, String> map, DTO dto) throws Exception {
		// DTO dto = dtoClass.newInstance();
		Iterator<String> it = map.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			String val = map.get(key);
			setValue(dto, key, val);
		}
		return dto;
	}
	
	public static <DTO> List<DTO> AuthRightMapToOutput(List<Map> maps, Class<DTO> dtoClass) throws Exception {
		if (maps == null || maps.size() == 0)
			return null;
		List<DTO> list = new ArrayList<DTO>();
		for (Map map : maps) {
			DTO dto = AuthRightMapToOutput(map, dtoClass);
			list.add(dto);
		}
		return list;
	}
	public static <DTO> DTO AuthRightMapToOutput(Map map, Class<DTO> dtoClass) throws Exception {
		if (map == null)
			return null;
		List<NativeQueryMapInfo> mapList = NativeQueryMapInfo.build(dtoClass);
		DTO dto = dtoClass.newInstance();
		for (NativeQueryMapInfo mapInfo : mapList) {
			if (mapInfo.isOutput()) {
				String ss=mapInfo.getNativeQueryField().toUpperCase();
				Object queryValue = map.get(mapInfo.getNativeQueryField());
				Method setter = AccessorUtils.findSetter(dto, mapInfo.getDtoField());
				if (queryValue == null || "".equals(queryValue.toString().trim())) {
					setter.invoke(dto, "");
					continue;
				}

				if (queryValue instanceof Integer || queryValue instanceof BigInteger) {
					queryValue = String.valueOf(queryValue);
				} else if (queryValue instanceof Timestamp || queryValue instanceof Date) {
					// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd
					// HH:mm:ss");
					SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
					queryValue = sdf.format(queryValue).toString();
				} else if (queryValue instanceof BigDecimal) {
					String tempStr = String.valueOf(queryValue);
					if (tempStr.indexOf(".") > 0) {
						DecimalFormat myformat = new DecimalFormat("#.##");
						queryValue = myformat.format(queryValue);
					} else {
						queryValue = String.valueOf(queryValue);
					}

				}
				setter.invoke(dto, queryValue);
			}
		}
		return dto;
	}
	
}
